package solver;

import java.util.List;

import java.awt.Toolkit;

import etm.core.configuration.EtmManager;
import etm.core.monitor.EtmMonitor;
import model.SolverEnvironment;
import solver.moves.AbstractComplexMove;
import util.Chronograph;

public class ProblemDecompositionMathSolver2 extends BaseSolver {

	static {
		String os_name = System.getProperty("os.name").toLowerCase();
		String os_arch = System.getProperty("os.arch").toLowerCase();
		if ((os_name.indexOf("win") >= 0) && (os_arch.indexOf("64") > 0))
			System.loadLibrary("libwin64//jnilinearsolver");
		else
			System.loadLibrary("jnilinearsolver");
	}

	private boolean useGRB;
	private String solverName = "solver unknown";
	private int nop = 40;
	private int noem = 15;
	private int non = 2;
	private int time_limit_single_problem = 5;

	public void setTime_limit_single_problem(int time_limit_single_problem) {
		this.time_limit_single_problem = time_limit_single_problem;
	}

	private int delta_m = 0;
	private int delta_p = 0;

	SelectionAgent4 sa4;
	SelectionAgent5 sa5;
	boolean includeDependencyConstraints = true;
	boolean saveLP = false;

	public void setLimitXVars(int limitXVars) {
		if (limitXVars != -1)
			sa4.setLimitXvars(limitXVars);
	}

	public void setSaveLP(boolean saveLP) {
		this.saveLP = saveLP;
	}

	public ProblemDecompositionMathSolver2(SolverEnvironment env,
			boolean useGRB, boolean visualize) {
		super(env);
		sa4 = new SelectionAgent4(env);
		sa5 = new SelectionAgent5(env);
		this.useGRB = useGRB;
		if (useGRB)
			solverName = "Gurobi";
		if (visualize)
			env.getProblem()
					.createMachinesOrderedListsByLocationAndNeighborhood();
	}

	public void setIncludeDependencyConstraints(
			boolean includeDependencyConstraints) {
		this.includeDependencyConstraints = includeDependencyConstraints;
		sa4.setDependencyConstraintsNeeded(includeDependencyConstraints);
	}

	public void set_nop_noem_non(int nop, int noem, int non) {
		this.nop = nop;
		this.noem = noem;
		this.non = non;
		if (this.non == 0)
			this.non = 1;
		else if (this.non > noem)
			this.non = noem;
		log.debug(String.format("NOP=%d, NOEM=%d, NON=%d", this.nop, this.noem,
				this.non));
	}

	public void revertToBestSolution() {
		env.setCurrentSolution(best_solution.copy());
		env.getFullValidator().computeCost();
	}

	public int solveWithTimeLimit(int seconds) {
		int consecutive_time_limit_fails = 0;
		int consecutive_time_limit_successes = 0;
		int improvement_counter = 0;
		Chronograph chronos = new Chronograph();
		chronos.reset();
//		env.setCurrentSolution(getCurrentSolution());
		getFullValidator().computeCost();
		long local_best_cost = getFullValidator().getInitialSolutionCost();

		chronos.lap();
		int escape_counter = 0;
		while (chronos.getElapsedTimeInSeconds() < seconds) {
			log.debug(String
					.format("###########################  ROUND %d  %d ###########################",
							iterations, escape_counter));
			log.debug(String.format("Current solution identity=%d",
					getCurrentSolution().identity));
			long time_left_seconds = seconds
					- chronos.getElapsedTimeInSeconds();
			log.debug(String.format("Time left = %d", time_left_seconds));		
			List<Integer> selectedProcesses;
			int number_of_selected_processes = nop + delta_p;
			int number_of_selected_machines = noem + delta_m;
			
//			selectedProcesses = sa4.selectFullRandomProcessesAndMachinesFast(
//					number_of_selected_processes, number_of_selected_machines, non);
			if(env.getRandom().nextBoolean())
			{
				selectedProcesses = sa4.selectFullRandomProcessesAndMachinesFast(
						number_of_selected_processes, number_of_selected_machines, non);
//				System.err.println("SA4 selected processes="+selectedProcesses.size());
			}
			else
			{
				selectedProcesses = sa5.selectAllProcessesofAHeavyMachinesFast(
						number_of_selected_processes, number_of_selected_machines, non);
//				System.err.println("SA5 selected processes="+selectedProcesses.size());
			}
			
			if(selectedProcesses.size()==0)
				continue;
			
			log.debug("\n"
					+ getProblem().getSelectedProcessesAndEligibleMachines(
							selectedProcesses, false, env));
			long cutoff = getFullValidator().getNewSolutionCost();
			BaseExtraLeanIPModelBuilder modelBuilder;
			while(AbstractComplexMove.isMathSolverBeenUsed)
				try {
					Thread.sleep(20);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			AbstractComplexMove.isMathSolverBeenUsed=true;	
			if (useGRB) {
				modelBuilder = new GrbExtraLeanIPModelBuilder(getProblem(),
						selectedProcesses, true, time_limit_single_problem,
						cutoff);
			} else {
				modelBuilder = new OrtExtraLeanIPModelBuilder(getProblem(),
						selectedProcesses, false, time_limit_single_problem,
						cutoff);
				solverName = ((OrtExtraLeanIPModelBuilder) modelBuilder)
						.getSolverVersion();
			}
			modelBuilder.setInitialSolution(getInitialSolution());
			modelBuilder.setCurrentSolution(getCurrentSolution());
			modelBuilder.includeDependencyHardConstraints(includeDependencyConstraints);

			boolean solved = modelBuilder.solve(saveLP);
			int solver_status = modelBuilder.getSolverStatus();
			if (solver_status == 0 || solver_status == 1) {
				modelBuilder.updateSolutionDirect(env, selectedProcesses);
				if (isIncomplete(getPotentialSolution())) {
					log.error("" + this.getClass().getSimpleName());
				} else {
					getFullValidator().computeCost();
					potential_cost = getFullValidator()
							.getNewSolutionCost();
					log.debug(String
							.format("***GLOBAL BEST COST=%,d POTENTIAL SOLUTION COST=%,d",
									local_best_cost, potential_cost));
					if (potential_cost > cutoff) {
						Toolkit.getDefaultToolkit().beep();
						log.error(String
								.format("Potential solution cost=%,d should never be greater than cutoff=%,d",
										potential_cost, cutoff));
						// System.exit(-1);
					}
										
					if(best_cost < potential_cost){ //Externally some one has set the best cost
							synchronized (best_solution) {
								env.setPotentialSolution(getBestSolution());
								potential_cost = env.getPotentialSolution().getCost();
							}
					}

					if (potential_cost <= best_cost) {
							if (getFullValidator().isFeasible()) {
								if (best_cost == potential_cost) {
									if (best_solution
											.isIdentical(getPotentialSolution()))
										log.debug(String
												.format(":| SAME solution (round=%d, improvement=%d, number of moved processes=%d) %,d",
														iterations,
														improvement_counter,
														getNumberOfMovedProcesses(getPotentialSolution()),
														potential_cost));
									else
										log.debug(String
												.format(":| EQUIVALENT solution found (round=%d, improvement=%d, number of moved processes=%d) %,d",
														iterations,
														improvement_counter,
														getNumberOfMovedProcesses(getPotentialSolution()),
														potential_cost));
								} 
								else 
								{
									improvement_counter++;
									log.info(String
											.format(":) GLOBAL BETTER solution found (round=%d, improvement=%d, number of moved processes=%d) %,d (reduced by %,d)",
													iterations,
													improvement_counter,
													getNumberOfMovedProcesses(getPotentialSolution()),
													potential_cost,
													best_cost
													- potential_cost));
								}
								if(!saveCurrentToBest()){ //Externally some one has set the best cost
									synchronized (best_solution) {
										env.setPotentialSolution(getBestSolution().copy());
										potential_cost = env.getPotentialSolution().getCost();
									}
								}
								local_best_cost = best_cost;
								setCurrentSolution(getPotentialSolution().copy());
								chronos.lap();
								// saveSolution(global_best_cost,
								// chronos.getElapsedTimeInSeconds(), false);
								escape_counter = 0;
							} 
							else {
								synchronized (best_solution) {
									env.setPotentialSolution(getBestSolution());
									potential_cost = env.getPotentialSolution().getCost();
								}
							}
					}
					consecutive_time_limit_fails = 0;
					consecutive_time_limit_successes++;
					if (consecutive_time_limit_successes == 50) {
						increaseSubproblemSize();
						consecutive_time_limit_successes = 0;
					}
				}
			} 
			else //IP Solver failed
			{
				log.debug("IP solver failed. A fatal error occured");
				escape_counter++;
				consecutive_time_limit_fails++;
				consecutive_time_limit_successes = 0;
				if (consecutive_time_limit_fails == 2) {
					decreaseSubproblemSize();
					consecutive_time_limit_fails = 0;
				}
				// System.exit(-1);
			}
			AbstractComplexMove.isMathSolverBeenUsed=false;
			iterations++;
			chronos.lap();
//			Problem.RANDOM_SEED = getRandom().nextLong();
		}//while
				
		setCurrentSolution(best_solution.copy());
		setBestSolution(best_solution.copy());
		
		return chronos.getElapsedTimeInSeconds();
	}

	private void increaseSubproblemSize() {
		sa4.changeLimitXVarsByPercent(0.05);
		delta_p += 5;
		delta_m++;
		log.debug(String.format(
				"DECREASE SUBPROBLEM SIZE CALLED: XVARS=%d NOP=%d NOM=%d",
				sa4.getLimitXvars(), nop + delta_p, noem + delta_m));
	}

	static final int MIN_XVARS = 250;
	static final int MIN_NOP = 30;
	static final int MIN_NOEM = 15;

	private void decreaseSubproblemSize() {
		if (sa4.getLimitXvars() > MIN_XVARS)
			sa4.changeLimitXVarsByPercent(-0.05);
		else
			sa4.setLimitXvars(MIN_XVARS);
		if (nop + delta_p < MIN_NOP)
			delta_p -= 5;
		else {
			nop = MIN_NOP;
			delta_p = 0;
		}
		if (noem + delta_m < MIN_NOEM)
			delta_m--;
		else {
			noem = MIN_NOEM;
			delta_m = 0;
		}
		log.debug(String.format(
				"DECREASE SUBPROBLEM SIZE CALLED: XVARS=%d NOP=%d NOM=%d",
				sa4.getLimitXvars(), nop + delta_p, noem + delta_m));
	}

	public int getRounds() {
		return (int)(iterations);
	}
}
